package com.rosca.converters.number2word.impl;

import com.rosca.converters.number2word.N2WConverter;
import com.rosca.converters.number2word.NumberTooLargeException;
import org.apache.log4j.Logger;

import java.util.Stack;

import static com.rosca.converters.number2word.utils.GroupScanner.scan;
import static com.rosca.converters.number2word.utils.StackUtils.join;

/**
 * User: vrosca
 * Date: 7/27/11
 * Time: 2:44 PM
 * &copy; PFA Rosca Val Dan Dar Ion 2011
 * <p>
 * Performs the tasks related to spelling numbers
 * in most languages that use the arabic numbering
 * system. Applies the Template method pattern
 * </p>
 */
public abstract class Arabic3DigitGroupConverter implements N2WConverter {
    /**
     * Used keywords (apart cardinal numbers)
     */
    protected enum Keyword {
        HUNDRED, THOUSAND, MILLION, MINUS, AND, ZERO
    }

    /**
     * Spells a 3-digit number on the spelling stack
     *
     * @param n3            current 3-digit group
     * @param groupIdx      index of group (0 - units, 1 - thousands, 2 - millions)
     * @param spelled       part of whole number that was already spelled
     * @param spellingStack stack containing spelled words
     */
    abstract void spellN3(int n3, int groupIdx, int spelled, Stack<String> spellingStack);

    /**
     * Spells a keyword from above, allowing subclasses to translate
     * in the required language (e.g. THOUSAND = "mille" in French)
     *
     * @param keyword the keyword to spell
     * @return the spelled keyword
     */
    abstract String spell(Keyword keyword);

    private static final Logger logger = Logger.getLogger(Arabic3DigitGroupConverter.class);

    /**
     * Spells a number
     *
     * @param number the number to spell
     * @return the spelled number
     * @throws NumberTooLargeException
     */
    public String spell(int number) throws NumberTooLargeException {
        if (number == 0)
            return spell(Keyword.ZERO); // spell zero immediately - simplifies algorithm
        else if (number < 0)
            return spell(Arabic3DigitGroupConverter.Keyword.MINUS) + " " + spell(-number); // spell negative numbers
        else if (number >= 1000000000) {
            logger.debug("Number \"" + number + "\" is too large to spell");
            throw new NumberTooLargeException(); // don't allow numbers greater than 999,999,999
        }

        Stack<String> spellingStack = new Stack<String>();
        int groupIdx = 0, // keep track of current group index (0 - units, 1 - thousands, 2 - millions)
                spelled = 0; // keep track of what was already spelled
        for (Integer n3 : scan(number, 3)) { // a 3-digit group extracted from the number, right-to-left
            spellN3(n3, groupIdx, spelled, spellingStack); // delegate spelling the group to a subclass
            spelled += n3 * (int) Math.pow(10, 3 * groupIdx++);
        }
        assert number == spelled;

        String spelling = join(spellingStack, " "); // join the spelling stack into a string
        logger.debug("Spelled \"" + number + "\" as \"" + spelling + "\"");
        return spelling;
    }
}
